//    Copyright (C) Kherty.  All rights reserved.
using System;
using System.Diagnostics.Contracts;
using System.IO;

namespace OpenLS.Spreadsheet.Formats.Biff
{
#if DEBUG
    public
#else
#endif
    struct RKNumber
    {
        private uint rkValue;

        private RKNumber(uint u)
        {
            this.rkValue = u;
        }
        public override string ToString()
        {
            bool valueChanged = (rkValue & 1) != 0;
            bool nonFloatingPointValue = (rkValue & 2) != 0;
            long encodedValue = rkValue >> 2;

            return string.Format("{0}[{1}, {2}, {3}] ({4})", GetType().Name, valueChanged, nonFloatingPointValue, encodedValue, DoubleValue);
        }

        public static explicit operator double(RKNumber value)
        {
            return value.DoubleValue;
        }
        public static explicit operator RKNumber(double value)
        {
#if DEBUG
          //\\  Contract.Ensures(AreClose((double)Contract.Result<RKNumber>(), value), value.ToString());
#endif
            double value100 = value*100;
            bool isNonFloating = Math.Floor(value) == value;
            bool isNonFloating100 = Math.Floor(value100) == value100;
            if (isNonFloating100 && value >= 0)
            {
                if (!isNonFloating)
                {
                    // check bounds
                    int l = (int) value100;
                    uint ui = (uint) (l << 2);
                    var result = new  RKNumber( ui | 0x1 | 0x2);
                    Contract.Assert(result.DoubleValue == value);
                    return result;
                }
                else
                {
                    int l = (int)value;
                    uint ui = (uint)(l << 2);

                    var result = new RKNumber(ui | 0x0 | 0x2);
                    Contract.Assert( result.DoubleValue == value);
                    return result;
                    
                }
            }
            else
            {
                long ul = BitConverter.DoubleToInt64Bits(value);
                var encodedValue  = ul >> 34;
                uint rkValue =  (uint) (encodedValue << 2);
                var result =  new RKNumber(rkValue);
#if DEBUG
                Contract.Assert(AreClose( result.DoubleValue , value));
#endif
                return result;

            }
        }

#if DEBUG
        public static bool AreClose(double value1, double value2)
        {
            if (value1 == value2)
            {
                return true;
            }
            double sum = ((Math.Abs(value1) + Math.Abs(value2)) + 10.0) * 2.2204460492503131E-6;

            double diff = value1 - value2;
            var result =  ((-sum < diff) && (sum > diff));
            Contract.Assert((result));
            return result;
        }
#endif
 


        double DoubleValue
        {
            get
            {
                bool valueChanged = (rkValue & 1) != 0;
                bool nonFloatingPointValue = (rkValue & 2) != 0;
                long encodedValue = rkValue >> 2;
                if (nonFloatingPointValue)
                {
                    double valueEncodedValue = encodedValue;
                    if (!valueChanged)
                    {
                        return valueEncodedValue;
                    }
                    return (valueEncodedValue/100);
                }
                double result = BitConverter.Int64BitsToDouble(encodedValue << 0x22);
                if (!valueChanged)
                {
                    return result;
                }
                return (result/100);
            }
        }


        internal void Read(BinaryReader reader)
        {
            rkValue = reader.ReadUInt32();
        }

        public void Write(BinaryWriter writer)
        {
            writer.Write(rkValue);
        }

        public static bool CanEncode(double value)
        {
            RKNumber num = (RKNumber) value;
            double dbl = (double) num;
            return dbl == value;

        }
    }
}